﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;

namespace ReversiBackend
{
    public delegate void GameOverHandler(int winner);

    /// <summary>
    /// Das Spielfeld
    /// </summary>
    public class Board : IEnumerable<int>
    {
        private int[,] field;
        /// <summary>
        /// Anzahl der Steine von Spieler 1 auf dem Feld
        /// </summary>
        public int P1Count { get; private set; }
        /// <summary>
        /// Anzahl der Steine von Spieler 2 auf dem Feld
        /// </summary>
        public int P2Count { get; private set; }
        public bool IsGameEnd { get { return this.CheckGameOver(); } }
        public int Winner { get {return (this.CheckGameOver())? this.GetWinner(): -999; } }


        public Board()
        {
          this.InitField(false);
        }


        public Board Copy()
        {
            Board newBoard = new Board();
            newBoard.P1Count = this.P1Count;
            newBoard.P2Count = this.P2Count;
            newBoard.field = new int[8,8];
            for (int i = 0; i < 8; i++)
            {
                for (int j = 0; j < 8; j++)
                {
                    newBoard.field[i, j] = this.field[i, j];
                }
            }
            return newBoard;
            
        }

        /// <summary>
        /// Setzt Feld und SpielerCounts auf ihre Anfangswerte
        /// </summary>
        private void InitField(bool setStartCells=true)
        {
            field = new int[8, 8];
            if (setStartCells)
            {
                field[3, 3] = -1;
                field[4, 4] = -1;
                field[3, 4] = 1;
                field[4, 3] = 1;
                P1Count = 2;
                P2Count = 2;
            }

            if (BoardChanged != null)
            {
                BoardChanged(null, false);
            }
        }

        /// <summary>
        /// Indexer für das Spielfeld.
        /// </summary>
        /// <param name="c">class Cell, die x und y enthält</param>
        /// <returns>den SpielerIndex des gewählten Feldes</returns>
        public int this[Cell c]
        {
            get
            {
                return field[c.X, c.Y];
            }
            set
            {
                if ((value == -1 || value == 1) && (this.IsValidMove(c, value)) )
                {
                    field[c.X, c.Y] = value;
                    if (value == -1)
                    {
                        P1Count++;
                    }
                    else
                    {
                        P2Count++;
                    }
                    ScanArea(c, true, value);
                    if (BoardChanged != null)
                    {
                        BoardChanged(c, true);
                    }
                    if (CheckGameOver())
                    {
                        if (GameOver != null)
                        {
                            GameOver(GetWinner());
                        }
                    }
                }
                else
                {
                    string s = string.Format("{0} -> ({1}, {2}) is not a valid move", value, c.X, c.Y);
                    //throw new ArgumentException(s);
                }
            }
        }

        /// <summary>
        /// Untersucht die Umgebung des angegebenen Feldes und dreht die eroberten Steine um, bzw. gibt zurück ob der Zug gültig ist
        /// </summary>
        /// <param name="c"></param>
        /// <param name="turnStones"></param>
        /// <param name="player"></param>
        /// <returns>Gültigkeit des Zuges</returns>
        private bool ScanArea(Cell c, bool turnStones, int player)
        {
            bool checkMove = false;
            List<Cell> stonesToTurn = new List<Cell>();
            int cx, cy;
            for (int x = -1; x < 2; x++)
            {
                for (int y = -1; y < 2; y++) //quasi richtungsvektoren v = (x, y)
                {
                    if (x == 0 && y == 0) continue;
                    cx = c.X; //startfeld nach jeder vektoränderung zurücksetzen
                    cy = c.Y;
                    stonesToTurn.Clear();  //werden nur umgedreht, wenn turnStones = true
                    while (true)
                    {
                        cx += x; //ein feld weiter in richtung des vektors
                        cy += y;
                        if (cx > 7 || cx < 0 || cy > 7 || cy < 0) break; //außerhalb des feldes

                        if (field[cx, cy] == player) 
                        {
                            if (stonesToTurn.Count != 0)//wenn eigener stein un dazwischen gegnersteine
                            {
                                checkMove = true;//in jedem fall ein gültiger zug 
                                if (turnStones) //ggf steine umdrehen
                                {
                                    foreach (Cell i in stonesToTurn)
                                    {
                                        field[i.X, i.Y] = player;
                                        if (BoardChanged != null)
                                        {
                                            BoardChanged(i, false);
                                        }
                                        //spielerpunkte anpassen:
                                        if (player == -1)
                                        {
                                            P1Count++;
                                            P2Count--;
                                        }
                                        else if (player == 1)
                                        {
                                            P1Count--;
                                            P2Count++;
                                        }
                                        else
                                        {
                                            throw new ArgumentException("Spieler muss -1 oder 1 sein");
                                        }
                                    }

                                }
                                else
                                {
                                    return checkMove; 
                                }
                                break; //in jedem fall aber diesen schleifendurchlauf abbrechen
                            }
                            else if (stonesToTurn.Count == 0) //wenn direkt eigener stein abbrechen
                            {
                                break; 
                            }
                        }
                        else if (field[cx, cy] == 0) //wenn leer (bevor etwas interessantes passiert ist) abbrechen
                        {
                            break;
                        }
                        else if (!(field[cx, cy] == player || field[cx, cy] == 0)) //fremder stein wird zum umdrehen gemerkt
                        {
                            stonesToTurn.Add(new Cell(cx, cy));
                        }
                    }

                }
            }
            return checkMove;
        }
        
        /// <summary>
        /// Ermittelt den Gewinner
        /// </summary>
        /// <returns></returns>
        private int GetWinner()
        {
            if (P2Count > P1Count)
            {
                return 1;
            }
            else if (P2Count == P1Count)
            {
                return 0;
            }
            else if (P2Count < P1Count)
            {
                return -1;
            }
            else
            {
                throw new Exception("Fehler in board.GetWinner();");
            }
        }

        /// <summary>
        /// interne Abfrage ob das Spiel beendet werden muss
        /// </summary>
        /// <returns>true = gameover, false = Spiel geht weiter</returns>
        private bool CheckGameOver()
        {
            bool check = true;
            foreach (int i in field)
            {
                if (i == 0) check = false;
            }
            if (GetValidMoves(1).Count == 0 && GetValidMoves(-1).Count == 0)
            {
                check = true;
            }
            if (P1Count == 0 || P2Count == 0)
            {
                check = true;
            }
            return check;
        }

        /// <summary>
        /// Setzt das Spielfeld zurück
        /// </summary>
        public void Clear()
        {
            this.InitField();
        }

        /// <summary>
        /// Gibt eine Liste aller derzeit gültigen Spielzüge für den angegebenen Spieler
        /// </summary>
        /// <param name="player"></param>
        /// <returns>Liste aller derzeit gültigen Spielzüge(erlaubte Felder)</returns>
        public List<Cell> GetValidMoves(int player)
        {
            if (player == -1 || player == 1)
            {
                List<Cell> valid = new List<Cell>();
                for (int x = 0; x < 8; x++)
                {
                    for (int y = 0; y < 8; y++)
                    {
                        Cell c = new Cell(x, y);
                        if (IsValidMove(c, player))
                        {
                            valid.Add(c);
                        }
                    }
                }
                return valid;
            }
            else
            {
                throw new ArgumentException("Player muss -1 oder 1 sein");
            }
        }

        /// <summary>
        /// Überprüft ob der Spieler auf das Feld setzen kann
        /// </summary>
        /// <param name="c">(x,y)</param>
        /// <param name="player">spieler-index</param>
        /// <returns></returns>
        public bool IsValidMove(Cell c, int player)
        {
            if (player == -1 || player == 1)
            {
                return this[c] == 0 && ScanArea(c, false, player);
            }
            else
            {
                throw new ArgumentException("Player muss -1 oder 1 sein");
            }
        }

        //events für GUI
        /// <summary>
        /// 
        /// </summary>
        /// <param name="m">die zu ändernde zelle</param>
        /// <param name="playerMove">gibt an ob der spieler den stein gesetzt hat,m oder ob er infolge dessen umgedreht wurde</param>
        public delegate void BoardChangedHandler(Cell? m, bool playerMove);

        public event BoardChangedHandler BoardChanged;

        public event GameOverHandler GameOver;

        #region IEnumerable<int> Members

        public IEnumerator<int> GetEnumerator()
        {
            return (IEnumerator<int>)field.GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return field.GetEnumerator();
        }

        #endregion

        #region Serialization stuff
        public void DumpData(Dictionary<string, object> data)
        {
            data.Add("field", field);
            data.Add("p1count", P1Count);
            data.Add("p2count", P2Count);
        }

        public void LoadData(Dictionary<string, object> data)
        {

            P1Count = (int)data["p1count"];
            P2Count = (int)data["p2count"];
            field = (int[,])data["field"] ;

            if (BoardChanged != null)
            {
                BoardChanged(null, false);
            }
        }
        #endregion
    }
}
